<?php

namespace App\Services\Sms;

use App\Enums\ResponseCode;
use App\Enums\SmsEnum;
use App\Exceptions\BusinessException;
use App\Helpers\ValidatorTool;
use App\Jobs\SmsSend;
use App\Models\SmsSendlog;
use App\Models\User;
use App\Services\AbstractBaseService;
use Illuminate\Support\Facades\Cache;
use Illuminate\Support\Facades\DB;

class SmsService extends AbstractBaseService
{

    /**
     * 发送短信(用户类型短信)
     * @param empty $mobile 手机号码或者邮箱
     * @param string $sendKey 模板标识
     * @param string $sendIp ip 地址
     * @param int $platform 平台。1-ios、2-android、3-wap、4-PC。
     * @param array $replaceArr 替换模板数组。一般不需要传，需传特殊值可用此参数。['PHOME' => 'xxx', 'USERNAME' => 'xxx']
     * @return void
     * @throws BusinessException
     */
    public static function send(mixed $mobile, string $sendKey, string $sendIp = '', int $platform = 0, array $replaceArr = [])
    {

        //以下场景需要鉴权
        if (in_array($sendKey, [SmsEnum::EDIT_PAY_PASS, SmsEnum::WRITE_OFF])) {
            $userInfo = auth('api')->user();
            if ($userInfo->mobile != $mobile){
                self::exception(ResponseCode::UNAUTHORIZED, trans('手机号码不一致'));
            }
            if (!$userInfo) {
                self::exception(ResponseCode::UNAUTHORIZED, trans('exception.授权过期请重新登录'));
            }
        } elseif ($sendKey == SmsEnum::CHANGE_MOBILE_OLD) {
            $userInfo = auth('api')->user();
            if (!$userInfo) {
                self::exception(ResponseCode::UNAUTHORIZED, trans('exception.授权过期请重新登录'));
            }
            //绑定手机号码
            if ($userInfo->mobile == $mobile){
                self::exception(ResponseCode::UNAUTHORIZED, trans('手机号码不能与当前号码一致'));
            }
            $findReg = User::query()->where(['mobile' => $mobile])->count();
            if ($findReg > 0) {
                self::exception(ResponseCode::VALIDATION_ERROR, trans('手机号码已使用'));
            }
        } else if ($sendKey == SmsEnum::FIND_PASSWORD) {
            //找回用户密码判断此手机号码是否注册
            $findReg = User::query()->where(['mobile' => $mobile])->count();
            if (!$findReg) {
                self::exception(ResponseCode::VALIDATION_ERROR, trans('手机号码或邮箱尚未注册'));
            }
        }
        $driveCut = 0;
        if (ValidatorTool::is_mobilephone($mobile)) {
            $driveCut = 1; //手机号码
        } else {
            self::exception(ResponseCode::VALIDATION_ERROR, trans('手机号码不正确'));
        }

        // [2] 内部手机号码不做发送限制。
        $isInsideAccount = VerifyService::isInsideAccount($mobile);
        if (!$isInsideAccount) {
            VerifyService::checkDayAccountTimes($mobile);
            VerifyService::checkDayIpTimes($sendIp, $sendKey, $driveCut);
            VerifyService::checkSendInterval($mobile);
            if (VerifyService::isBlackListAccount($mobile)) {
                self::exception(ResponseCode::SERVICE_UNAVAILABLE, trans('您的账号暂时无法接收短信'));
            }
        }
        // [3] 开发机码验证码为 123456.
        $code = rand(100000, 999999);
        if (config('app.debug')) {
            $code = 123456;
        }
        // 获取发送模板
        $replaceArr ["TIME"] = 5;
        $result = self::getReplaceContent($sendKey, $code, $replaceArr);
        // 创建短信日志
        $datetime = now();
        $data = [
            'mobile' => $mobile,
            'content' => $result['content'],
            'tpl_id' => $result['tpl_id'],
            'verify_code' => $code,
            'ip' => $sendIp,
            'created_at' => $datetime,
            's_time' => $datetime,
            'sms_status' => SmsEnum::SEND_STATUS_CREATE,
            'platform' => $platform,
            'drive_cut' => $driveCut
        ];
        $insertId = Db::table('sms_sendlog')->insertGetId($data);
        if (!$insertId) {
            self::exception(ResponseCode::SERVICE_UNAVAILABLE, '短信发送失败');
        }
        self::clearVerifyTimesKey($mobile);
        $queueData = [
            'id' => $insertId,
            'mobile' => $mobile,
            'code' => $code,
            'content' => $result['content'],
            'drive_cut' => $driveCut,
            'time_second' => $replaceArr ["TIME"],
            'is_send' => config('sms.is_send_sms')
        ];
        dispatch(new SmsSend($mobile, $queueData));
    }

    /**
     * 短信模板替换。
     *
     * @param string $smsType 关键字
     * @param string $code 验证码/免密自动注册时为密码。
     * @param array $replaceArr 扩展短信字段
     *
     * -- eg:start --
     * $replaceArr = ['username' => 'fingerQin', 'sex' => '覃先生']
     * 将替换模板中的 %USERNAME% 为 fingerQin, %sex% 替换为 覃先生。
     * -- eg:end --
     *
     * @return array []
     * @throws BusinessException
     */
    private static function getReplaceContent(string $smsType, string $code = '', array $replaceArr = []): array
    {
        $template = self::getTemplet($smsType);
        $keys = ['%CODE%'];
        $values = [$code];
        if (!empty($replaceArr)) {
            $arr = array_change_key_case($replaceArr, CASE_UPPER); // 键名转大写
            foreach ($arr as $k => $item) {
                $keys[] = '%' . $k . '%';
                $values[] = $item;
            }
        }
        // 短信内容替换
        $content = str_replace($keys, $values, $template['content']);
        return ['tpl_id' => $template['id'], 'content' => $content];
    }

    /**
     * 获取发送模板
     *
     * @param string $sendKey 短信模板标识
     * @return array
     * @throws BusinessException
     */
    public static function getTemplet(string $sendKey): array
    {
        $result = Db::table('sms_tpl')->where(['send_key' => $sendKey])->first();
        if (empty($result)) {
            self::exception(ResponseCode::SERVICE_UNAVAILABLE, trans('短信模板不存在'));
        }
        $sms_body = $result->sms_body;
        return ['content' => $sms_body, 'id' => $result->id, 'send_key' => $result->send_key];
    }

    /**
     * 短信验证码验证成功清除计数器缓存 KEY。
     *
     * @param string $mobile
     * @return void
     */
    private static function clearVerifyTimesKey(string $mobile)
    {
        $cacheKey = "sms-times:{$mobile}";
        Cache::forget($cacheKey);
    }

    /**
     * 验证短信验证码
     *
     * @param string $mobile 手机号码
     * @param int $verifyCode 验证码
     * @param string $sendKey 短信模板对应的标识
     * @param int $isDestroy 是否销毁 1 - 销毁 0 - 不销毁。
     * @param string $ip IP 地址。用于检测两次 IP 位置是否异常。
     *
     * @return void
     * @throws BusinessException
     */
    public static function verify(string $mobile, int $verifyCode, string $sendKey, int $isDestroy = 1, string $ip = '')
    {
        if (!empty(config('app.verify_code')) && $verifyCode == config('app.verify_code')) {
            return;
        }
        if (strlen($sendKey) == 0) {
            self::exception(ResponseCode::SERVICE_UNAVAILABLE, trans('短信模板标识不能为空'));
        }
        $template = self::getTemplet($sendKey);
        $smsLog = SmsSendlog::query()->where(['tpl_id' => $template['id'], 'mobile' => $mobile])->orderByDesc('id')->first();
        if (empty($smsLog)) {
            self::exception(ResponseCode::SERVICE_UNAVAILABLE, trans('您的验证码不正确'));
        }
        $expireTime = strtotime('+10 minutes', strtotime($smsLog['created_at']));
        if (($smsLog['cksms'] == SmsEnum::STATUS_USED) && (strtotime('now') > $expireTime)) {
            self::exception(ResponseCode::SERVICE_UNAVAILABLE, trans('请重新获取验证码'));
        }
        if ($smsLog['cksms'] == SmsEnum::STATUS_USED) {
            self::exception(ResponseCode::SERVICE_UNAVAILABLE, trans('验证码已使用'));
        }
        if ($smsLog['cksms'] == SmsEnum::STATUS_INVALID) {
            self::exception(ResponseCode::SERVICE_UNAVAILABLE, trans('验证码已失效'));
        }
        if (time() > $expireTime) {
            self::exception(ResponseCode::SERVICE_UNAVAILABLE, trans('您的验证码已失效,请重新获取!'));
        }
        $ip = empty($ip) ? request()->getClientIp() : $ip;
        if ($ip != $smsLog['ip']) {
            self::exception(ResponseCode::SERVICE_UNAVAILABLE, trans('您的 IP 位置异常'));
        }
        if ($smsLog['verify_code'] != $verifyCode) {
            self::exception(ResponseCode::SERVICE_UNAVAILABLE, trans('您的验证码不正确'));
        }
        if ($isDestroy) {
            SmsSendlog::where(['id' => $smsLog['id']])->update(['cksms' => SmsEnum::STATUS_USED]);
        }
        self::clearVerifyTimesKey($mobile);
    }

    /**
     * 短信验证码验证次数判断。
     *
     * @param string $mobile 账号信息。
     * @return bool true-未受限、false-受限。
     */
    private static function verifyTimes(string $mobile)
    {
        $verifyTimes = config('sms.verify_times');
        $cacheKey = "sms-times:{$mobile}";
        $times = Cache::get($cacheKey);
        if ($times > 0 && $times >= $verifyTimes) {
            self::clearVerifyTimesKey($mobile);
            return false;
        } else {
            if ($times) { // 存在则自增1
                Cache::increment($cacheKey, 1);
            } else {
                Cache::put($cacheKey, 2, 60);
            }
            return true;
        }
    }
}
